/*
 * DateUtils.java
 *
 * Created on 26 May 2006, 21:30
 * 
 * Copyright (C)
 *
 * This program is free software; you can redistribute it and/or modify it under 
 * the terms of the GNU General Public License as published by the Free Software 
 * Foundation; either version 2 of the License, or (at your option) any later 
 * version.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT 
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 
 * details.
 * 
 * You should have received a copy of the GNU General Public License along with 
 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple 
 * Place, Suite 330, Boston, MA 02111-1307 USA
 */

package com.grubby.utils;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Static methods to perform common tasks relating to dates.
 * @author A. HARMEL-LAW. E: andrew.harmel.law@gmail.com
 * @version 0.1
 */
public class DateUtils {
    
    public static final Logger LOGGER 
            = Logger.getLogger("com.grubby.utils.DateUtils");
    
    /**
     * Returns the <Date> right now as a <String> in the form dd/MM/yyyy
     * @return The <Date> right now as a String of format dd/MM/yyyy
     */
    public static String getDateNowString() {
        
        // Get a dummy date (right now)
        Calendar myNowTime = Calendar.getInstance();
        
        // Create a simple date formatter
        SimpleDateFormat mySDF = new SimpleDateFormat("dd/MM/yyyy");
        
        // Return the time now as a date
        return mySDF.format(myNowTime.getTime());
        
    }
    
    /**
     * Returns a random <Date> of birth as a dd/MM/yyyy String.  The <Date> will
     * be between the maximum and minimum ages provided.
     * <P>
     * Nullsafe - NULL will be returned if the <Random> object passed as a 
     * parameter is null.
     * <P>
     * NOTE: This is not quite perfectly random but does the job.
     * @param minAge The minimum age required
     * @param maxAge The maximum age required
     * @param rand The seeded <Random> to use to generate the DOB <Date>
     * @return The date of birth in the format dd/MM/yyyy
     */
    public static String getRandomDOB(int minAge, int maxAge, Random rand) {
        
        // Check for null
        if (rand == null) {
            LOGGER.log(Level.WARNING, "The random object passed as an argument " +
                    "was null.  Returning NULL.");
            return null;
        }
        
        // Get the <Date> today (right now)
        Calendar myRandomDOB = GregorianCalendar.getInstance();
        
        // Calc a random age between the requested max and min
        int age = rand.nextInt(maxAge - minAge) + minAge;
        
        // Set the time now back the required number of years
        myRandomDOB.set(Calendar.YEAR, myRandomDOB.get(Calendar.YEAR) - age);
        
        // Now scroll back month and day randomly too
        // NOTE: This is not perfect - the day rollback is based on a case
        // which always works (i.e. 28)
        int numMonthsBack = rand.nextInt(11);
        myRandomDOB.roll(Calendar.MONTH, -numMonthsBack);
        int numDaysBack = rand.nextInt(28);
        myRandomDOB.roll(Calendar.DAY_OF_MONTH, -numDaysBack);
        
        // Return the date as a formatted String
        SimpleDateFormat mySDF = new SimpleDateFormat("dd/MM/yyyy");
        return mySDF.format(myRandomDOB.getTime());
        
    }
    
    /**
     * Returns a random <Date> within the <Date> range provided.  
     * <P>
     * Nullsafe - If one of the arguments is null or the start <Date> is 
     * after the end <Date> then NULL will be returned.
     * @param startDate The <Date> which the new <Date> must be equal to or after
     * @param endDate The <Date> which the new <Date> must be equal to or before
     * @param rand The seeded <Random> to use to get the random <Date>
     * @return The random <Date>
     */
    public static Date getRandomDateWithinRange(Date startDate, Date endDate,
            Random rand) {
        
        // If either of the <Date>s or the <Random> is null, return <NULL>
        if (startDate == null | endDate == null | rand == null) {
            LOGGER.log(Level.WARNING, "One of the arguments provided was null!" +
                    " Returning NULL.");
            return null;
        }
        
        // Check the end data is after the start date
        if (startDate.after(endDate)) {
            LOGGER.log(Level.WARNING, "The start date was after the end date. " +
                    "Returning NULL.");
            return null;
        }
        
        // If the dates are the same date, return a new date of the same time
        if (startDate.getTime() == endDate.getTime()) {
            return new Date(startDate.getTime());
        }
        
        // Get a random number between the start date and end date
        long randomDateLong = RandomGenerators.getRandom(startDate.getTime(),
                endDate.getTime(), rand);
        
        // Create the date and return it
        return new Date(randomDateLong);
        
    }
    
    /**
     * Returns a <Date>, offset from the present system time offset by the number
     * of milliseconds requested. Millis can be positive (and the <Date> will be
     * in the future) or negative (and the <Date> will be in the past)
     * @param offset The amount to offset the generated <Date> from the current
     * system time.  Can be positive or negative
     * @return The <Date> requested
     */
    public static Date getDateOffsetFromNow(long offset) {
        
        // If the offset is not an offset, return the <Date> now
        if (offset == 0) {
            return new Date();
        }
        
        // Else return the offset <Date>
        return new Date(System.currentTimeMillis() + offset);
        
    }
    
    /**
     * Takes a number of days and returns the equivalent number of milliseconds.  
     * <P>
     * NOTE: Negative values still produce positive results.
     * @param numDays The number of days
     * @return The number of milliseconds the provided number of days represents
     */
    public static long calcDaysAsMillis(long numDays) {

        // If the number of days is negative...
        if (numDays > 0)
            numDays = numDays * -1; // ...make it positive
        
        // days -> hours(24) -> mins(60) -> secs(60) -> millis(1000)
        return numDays * 24L * 60L * 60L * 1000L;
        
    }
    
    /**
     * Subtract the number of days requested from the <Date> provided.
     * <P>
     * This is a non destructive method.  It does not alter the <Date> object
     * passed as a parameter.  
     * <P>
     * Nullsafe - If the <Date> argument is null then NULL will be returned.
     * @param subtractedFrom The <Date> to subtract the days from
     * @param numDays The number of days to subtract
     * @return The resulting <Date>
     */
    public static Date subtractDays(Date subtractedFrom, int numDays) {
        
        // If either of the dates is null, return null
        if (subtractedFrom == null) {
            LOGGER.log(Level.WARNING, "The date provided was null! " +
                    "Returning NULL.");
            return null;
        }
        
        if (numDays ==0) {
            return new Date(subtractedFrom.getTime());
        }
        
        return new Date(subtractedFrom.getTime() - calcDaysAsMillis(numDays));
        
    }
    
    /**
     * Generates a <List> of <Date> objects which range over a specified period 
     * of time. They begin at the start <Date> provided, and the remaining 
     * occur evenly spread over the duration.  If one instant point is requested
     * then the reurned <List> will be of size(1) and this item will be a <Date>
     * the same as the provided start date (i.e. the duration will be ignored).
     * <P>
     * Nullsafe - if the start <Date> is then NULL will be returned.
     * @param aStartDate The <Date> for the first <Date> in the list
     * @param millisDuration The duration across which the <Dates> must be ranged
     * @param noOfInstantPoints The number of <Dates> to generate between the 
     * start <Date> over the period requested.
     * @return The list of <Date>s as requested
     */
    public static List<Date> createDateRangeList(Date aStartDate, 
            long millisDuration, int noOfInstantPoints) {
        
        // If the start <Date> is null, return NULL
        if (aStartDate == null) {
            LOGGER.log(Level.WARNING, "The date provided was null!. " +
                    "Returning NULL.");
            return null;
        }
        
        // If the number of points requested is <= 0, return NULL
        if (noOfInstantPoints <= 0) {
            LOGGER.log(Level.WARNING, "The number of requested instant points" +
                    " was <= 0: " + noOfInstantPoints + ". Returning NULL.");
            return null;
        }
        
        List myDateRangeList = new ArrayList();
        
        // If there is only to be one element in this range
        if (noOfInstantPoints == 1) {
            myDateRangeList.add(new Date(aStartDate.getTime()));
            return myDateRangeList;
        }
        
        // Calculate how long the duration between each instant in the range should be
        long intraInstantDuration = millisDuration / (noOfInstantPoints - 1);
        
        // Create the instants and put them in the list
        long currentInstantMillis = aStartDate.getTime();
        for (int i = 0 ; i < noOfInstantPoints ; i ++) {
            
            // Create and store our latest instant
            Date currentInstant = new Date(currentInstantMillis);
            myDateRangeList.add(currentInstant);
            
            // Increment our instant counter
            currentInstantMillis += intraInstantDuration;
            
        }
        
        return myDateRangeList;
        
    }
    
} // end of class DateUtils.java